package dsx.netServer;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map.Entry;

import dsx.config.Config;
import dsx.config.ConfigItem;
import dsx.console.DsxConsole;
import dsx.inf.IDataTransfer;
import dsx.inf.INetServer;
import dsx.inf.ISlicer;
import dsx.iot._IotPipe;
import dsx.log.ServLog;
import dsx.system.ThreadEx;

public class UdpServer implements INetServer {

	private boolean running = false;
	private _UdpConfig udpConfig = null;
	private String serverName = "";
	IDataTransfer dataTransfer = null;
	ISlicer slicer = null;
	DatagramSocket sock = null;

	DatagramPacket recvPacket = new DatagramPacket(new byte[1500], 1500);
	HashMap<String, UdpClientMark> clientMap = new HashMap<String, UdpClientMark>();
	UdpServerThread serverThrd = new UdpServerThread();
	int connNum = 0;

	@Override
	public String getConfig() {
		synchronized (this) {
			if (udpConfig != null)
				return String.format(
						"protocol:udp; port:%d;maxConNum:%d;timeout:%ds",
						udpConfig.getPort(), udpConfig.getMaxConNum(),
						udpConfig.getTimeout());
			else
				return "";
		}
	}

	@Override
	public int getStatus() {
		if (running)
			return 1;
		else
			return 0;
	}

	@Override
	public void init(String serverName, IDataTransfer dataTransfer,
			ISlicer slicer) {
		synchronized (this) {
			this.serverName = serverName;
			this.dataTransfer = dataTransfer;
			this.slicer = slicer;
			this.udpConfig = loadConfig(); // 预读参数，在服务未启动时，可以被上层调用读取到参数
		}
	}

	@Override
	public boolean send(Object pipe, byte[] answ) {
		if (answ == null)
			return false;
		byte[] sendBytes;
		if (slicer == null)
			sendBytes = answ;
		else
			sendBytes = slicer.pack(answ);
		_IotPipe iotPipe = (_IotPipe) pipe;
		String clientKey = createClientKey(iotPipe.getHost(), iotPipe.getPort());
		try {
			UdpClientMark udpClientMark;
			synchronized (clientMap) {
				udpClientMark = clientMap.get(clientKey);
			}
			if (udpClientMark != null) {
				DatagramPacket sendPacket = new DatagramPacket(sendBytes,
						sendBytes.length, udpClientMark.address, udpClientMark.port);
				sock.send(sendPacket);
				return true;
			}
		} catch (Exception ex) {
		}
		return false;
	}

	@Override
	public boolean start() {
		synchronized (this) {
			if (this.running)
				return true;
			try {
				this.sock = new DatagramSocket(udpConfig.getPort());
				this.serverThrd.udpServer = this;
				this.serverThrd.setInterval(0);
				this.serverThrd.start();
				this.running = true;
				DsxConsole.out(getConfig(), "net udp server start",
						ServLog.info);
				return true;
			} catch (Exception ex) {
				DsxConsole.out(ex.getMessage() + "." + getConfig(),
						"net udp server start", ServLog.error);
				return false;
			}
		}
	}

	@Override
	public void stop() {
		synchronized (this) {
			if (!this.running)
				return;
			else {
				try {
					sock.close();
					this.serverThrd.stopEx();
					clientMap.clear();
					connNum = 0;
					this.running = false;
				} catch (Exception ex) {
				} finally {
				}
			}
		}
	}

	private _UdpConfig loadConfig() {
		_UdpConfig udpConfig = new _UdpConfig();
		ArrayList<ConfigItem> configItems = Config.get(this.serverName);
		for (int i = 0; i < configItems.size(); i++) {
			switch (configItems.get(i).name) {
			case "port":
				udpConfig.setPort(Integer.parseInt(configItems.get(i).value));
				break;
			case "timeout":
				udpConfig
						.setTimeout(Integer.parseInt(configItems.get(i).value));
				break;
			case "maxConNum":
				udpConfig
						.setMaxConNum(Integer.parseInt(configItems.get(i).value));
				break;
			default:
				break;
			}
		}
		return udpConfig;
	}

	private void checkAndAddClient(InetAddress address, int port) {
		String clientKey = createClientKey(recvPacket.getAddress()
				.getHostAddress(), recvPacket.getPort());
		synchronized (clientMap) {
			long now = new Date().getTime();
			UdpClientMark udpClientMark = clientMap.get(clientKey);
			if (udpClientMark != null) {
				udpClientMark.lastCommTime = now;
			} else {
				if (connNum >= udpConfig.maxConNum)
					clearClientMap();
				udpClientMark = new UdpClientMark();
				udpClientMark.address = address;
				udpClientMark.port = port;
				udpClientMark.lastCommTime = now;
				clientMap.put(clientKey, udpClientMark);
				connNum++;
			}
		}
	}

	private String createClientKey(String address, int port) {
		return address + Integer.toString(port);
	}

	private void clearClientMap() {
		long now = new Date().getTime();
		synchronized (clientMap) {
			for (Entry<String, UdpClientMark> entry : clientMap.entrySet()) {
				if (now - entry.getValue().lastCommTime > udpConfig.timeout * 1000) {
					clientMap.remove(entry.getKey());
				}
			}
		}
	}

	private class UdpClientMark {
		InetAddress address;
		int port;
		long lastCommTime;
	}

	private class UdpServerThread extends ThreadEx {

		UdpServer udpServer = null;

		@Override
		protected void doInLoop() {
			try {
				udpServer.sock.receive(udpServer.recvPacket);
				if (udpServer.recvPacket.getLength() > 0) {
					byte[] recvData = Arrays.copyOfRange(
							udpServer.recvPacket.getData(), 0,
							udpServer.recvPacket.getLength());
					udpServer.checkAndAddClient(
							udpServer.recvPacket.getAddress(),
							udpServer.recvPacket.getPort());
					_IotPipe iotPipe = new _IotPipe();
					iotPipe.setHost(udpServer.recvPacket.getAddress()
							.getHostAddress());
					iotPipe.setPort(udpServer.recvPacket.getPort());
					udpServer.dataTransfer.recv(iotPipe, recvData);
				}
			} catch (Exception ex) {

			}
		}
	}

}
